/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//mat.hpp

#ifndef MVL_MAT_HPP
#define MVL_MAT_HPP

#include "base.hpp"
#include "expr.hpp"
#include "layout.hpp"
#include "mat_layout.hpp"
#include "mat_mat_assign.hpp"
#include "assign.hpp"

namespace mvl {

template<typename T, typename L>
class mat: public expr<mat<T, L> >
{
public:
    typedef T                                       value_type;
    typedef T&                                      reference;
    typedef T const&                                const_reference;
    typedef T*                                      iterator;
    typedef T const*                                const_iterator;
    typedef std::reverse_iterator<iterator>         reverse_iterator;
    typedef std::reverse_iterator<const_iterator>   const_reverse_iterator;

public:
    typedef mat                         self_t;
    typedef self_t const&               const_closure_t;
    typedef self_t&                     closure_t;
    typedef L                           layout_t;
    typedef typename layout_t::order    order;
    typedef typename layout_t::Rows     Rows;
    typedef typename layout_t::Cols     Cols;
    typedef typename layout_t::Size     Size;

public:
    //stl
    static std::size_t size()               { return Size::value; }
    static std::size_t max_size()           { return Size::value; }
    static bool empty()                     { return false; }

    iterator begin()                        { return m_data; }
    iterator end()                          { return m_data + Size::value; }
    const_iterator begin() const            { return m_data; }
    const_iterator end() const              { return m_data + Size::value; }
    reverse_iterator rbegin()               { return reverse_iterator(end()); }
    reverse_iterator rend()                 { return reverse_iterator(begin()); }
    const_reverse_iterator rbegin() const   { return const_reverse_iterator(end()); }
    const_reverse_iterator rend() const     { return const_reverse_iterator(begin()); }

    value_type front()                      { return m_data[0]; }
    value_type back()                       { return m_data[Size::value - 1]; }
    const_reference front() const           { return m_data[0]; }
    const_reference back() const            { return m_data[Size::value - 1]; }

public:
    //
    template<typename E> 
    mat& operator = (expr<E> const& rhs) {
        MVL_STATIC_CHECK((E::Rows::value == Rows::value), "mat_size_mismatch");
        MVL_STATIC_CHECK((E::Cols::value == Cols::value), "mat_size_mismatch");
        mat_mat_assign<mat, E, fn_assign<value_type, value_type> >::apply(*this, rhs());
        return *this;
    }  

    mat& operator = (mat const& rhs) {
        mat_mat_assign<mat, mat, fn_assign<value_type, value_type> >::apply(*this, rhs);
        return *this;
    }  

public:

    //data access
    //IMPORTANT: if the layout is sparse, ONLY entries with a "_v_" tag are stored internally 
    // in a continuous chunk of memory and can be accessed through the following functions
    // 
    // If the layout is dense, the behaviour is as expected
    // 
    value_type operator()(size_t i) const {
        MVL_CHECK(i < Size::value, "index_out_of_bound");
        return m_data[i];
    }

    value_type& operator()(size_t i) {
        MVL_CHECK(i < Size::value, "index_out_of_bound");
        return m_data[i];
    }

    value_type operator()(size_t i, size_t j) const {
        return m_data[index(i, j, order())];
    }

    value_type& operator()(size_t i, size_t j) {
        return m_data[index(i, j, order())];
    }

protected:
    size_t index(size_t i, size_t j, layout::row_major_tag) const { 
        size_t idx = i * Cols::value + j;
        MVL_CHECK(idx < Size::value, "index_out_of_bound");
        return idx;
    }
    size_t index(size_t i, size_t j, layout::column_major_tag) const { 
        size_t idx = j * Rows::value + i; 
        MVL_CHECK(idx < Size::value, "index_out_of_bound");
        return idx;
    }

public:
    //constructors
    explicit mat() {}

    mat(mat const& m)
    {
        *this = m;
    }

    template<typename E>
    explicit mat(expr<E> const& e)
    {
        *this = e;
    }

    explicit mat(value_type v0) {
        assign(*this) = v0;
    }

    explicit mat(value_type v0, value_type v1) {
        assign(*this) = v0, v1;
    }

    explicit mat(value_type v0, value_type v1, value_type v2) {
        assign(*this) = v0, v1, v2;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3) {
        assign(*this) = v0, v1, v2, v3;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4) {
        assign(*this) = v0, v1, v2, v3, v4;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5) {
        assign(*this) = v0, v1, v2, v3, v4, v5;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9, value_type v10) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9, value_type v10, value_type v11) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9, value_type v10, value_type v11, value_type v12) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9, value_type v10, value_type v11, value_type v12, value_type v13) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9, value_type v10, value_type v11, value_type v12, value_type v13, value_type v14) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
    }

    explicit mat(value_type v0, value_type v1, value_type v2, value_type v3, value_type v4, value_type v5, value_type v6, value_type v7,
                 value_type v8, value_type v9, value_type v10, value_type v11, value_type v12, value_type v13, value_type v14, value_type v15) {
        assign(*this) = v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15;
    }

private:

    template<typename Entry>
    struct sparse_index : Entry::index {};

    template<typename Entry>
    struct dense_index {
        MVL_STATIC_CHECK((meta::less<typename Entry::i, Rows>::value), "tml_index_out_of_bound");
        MVL_STATIC_CHECK((meta::less<typename Entry::j, Cols>::value), "tml_index_out_of_bound");
        typedef typename meta::if_<
            typename meta::is_same<
                typename L::order, 
                layout::row_major_tag
            >::type,
            meta::int_<Entry::i::value * Cols::value + Entry::j::value>,
            meta::int_<Entry::j::value * Rows::value + Entry::i::value>
        >::type type;
    };

    template<typename Entry>
    struct get_index {
        MVL_STATIC_CHECK((meta::is_same<typename Entry::tag, layout::_v_>::value), "tml_entry_not_a_value");
        typedef typename meta::eval_if<
            typename meta::is_same<
                typename L::tag,
                layout::sparse_layout_tag
            >::type,
            sparse_index<Entry>,
            dense_index<Entry>
        >::type type;

        MVL_STATIC_CHECK((meta::greater_equal<type, meta::int_<0> >::value), "tml_index_out_of_bound");
        MVL_STATIC_CHECK((meta::less<type, Size >::value), "tml_index_out_of_bound");
    };

public:

    //expression access
    template<typename Entry>
    T at() const {
        typedef typename get_index<Entry>::type index;
        return m_data[index::value];
    }

    template<typename Entry>
    T& at() {
        typedef typename get_index<Entry>::type index;
        return m_data[index::value];
    }

private:
    T   m_data[Size::value];
};

} //namespace mvl

#endif

